#include "libfdt_env.h"
#include "fdt.h"
#include "libfdt.h"

#include "linux/string.h"
#include "linux/common.h"

char *fdt_string( void *fdt, int stroffset)
{
    return ( char *)fdt + fdt_off_dt_strings(fdt) + stroffset;
}

static int _fdt_string_eq( void *fdt, int stroffset,
                           char *s, int len)
{
     char *p = fdt_string(fdt, stroffset);

    return (strlen(p) == len) && (memcmp(p, s, len) == 0);
}

static int _nextprop( void *fdt, int offset)
{
    uint32_t tag;
    int nextoffset;

    do
    {
        tag = fdt_next_tag(fdt, offset, &nextoffset);

        switch (tag)
        {
        case FDT_END:
            if (nextoffset >= 0)
                return -FDT_ERR_BADSTRUCTURE;
            else
                return nextoffset;

        case FDT_PROP:
            return offset;
        }
        offset = nextoffset;
    } while (tag == FDT_NOP);

    return -FDT_ERR_NOTFOUND;
}

int _fdt_check_node_offset( void *fdt, int offset)
{
    if ((offset < 0) || (offset % FDT_TAGSIZE) || (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE))
        return -FDT_ERR_BADOFFSET;

    return offset;
}

int _fdt_check_prop_offset( void *fdt, int offset)
{
    if ((offset < 0) || (offset % FDT_TAGSIZE) || (fdt_next_tag(fdt, offset, &offset) != FDT_PROP))
        return -FDT_ERR_BADOFFSET;

    return offset;
}


int fdt_next_property_offset( void *fdt, int offset)
{
    if ((offset = _fdt_check_prop_offset(fdt, offset)) < 0)
        return offset;

    return _nextprop(fdt, offset);
}

int fdt_first_property_offset( void *fdt, int nodeoffset)
{
    int offset;

    if ((offset = _fdt_check_node_offset(fdt, nodeoffset)) < 0)
        return offset;

    return _nextprop(fdt, offset);
}

struct fdt_property *fdt_get_property_by_offset( void *fdt, int offset, int *lenp)
{
    int err;
     struct fdt_property *prop;

    if ((err = _fdt_check_prop_offset(fdt, offset)) < 0)
    {
        if (lenp)
            *lenp = err;
        return NULL;
    }

    prop = _fdt_offset_ptr(fdt, offset);

    if (lenp)
        *lenp = fdt32_to_cpu(prop->len);

    return prop;
}

struct fdt_property *fdt_get_property_namelen(void *fdt, int offset, char *name, int namelen, int *lenp)
{
    for (offset = fdt_first_property_offset(fdt, offset); (offset >= 0); (offset = fdt_next_property_offset(fdt, offset)))
    {
         struct fdt_property *prop;

        if (!(prop = fdt_get_property_by_offset(fdt, offset, lenp)))
        {
            offset = -FDT_ERR_INTERNAL;
            break;
        }
        if (_fdt_string_eq(fdt, fdt32_to_cpu(prop->nameoff), name, namelen))
            return prop;
    }

    if (lenp)
        *lenp = offset;

    return NULL;
}

void *fdt_getprop_namelen( void *fdt, int nodeoffset,  char *name, int namelen, int *lenp)
{
     struct fdt_property *prop;

    prop = fdt_get_property_namelen(fdt, nodeoffset, name, namelen, lenp);
    if (!prop)
        return NULL;

    return prop->data;
}

void *fdt_getprop_by_offset( void *fdt, int offset,  char **namep, int *lenp)
{
    struct fdt_property *prop;

    prop = fdt_get_property_by_offset(fdt, offset, lenp);
    if (!prop)
        return NULL;
    if (namep)
        *namep = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
    return prop->data;
}

void *fdt_getprop( void *fdt, int nodeoffset,  char *name, int *lenp)
{
    return fdt_getprop_namelen(fdt, nodeoffset, name, strlen(name), lenp);
}

char *fdt_get_name( void *fdt, int nodeoffset, int *len)
{
	 struct fdt_node_header *nh = _fdt_offset_ptr(fdt, nodeoffset);
	int err;

	if (((err = fdt_check_header(fdt)) != 0) || ((err = _fdt_check_node_offset(fdt, nodeoffset)) < 0))
			goto fail;

	if (len)
		*len = strlen(nh->name);

	return nh->name;

 fail:
	if (len)
		*len = err;
	return NULL;
}
